<template>
    <div style="border: 1px solid #ddd; background: #fff">
        <div style="background: #f5f5f5; border-bottom: 1px solid #ddd; padding: 10px 5px">
            <span style="color: #06c; font-weight: bold">WebSocket语音通话聊天</span>
            <span style="font-size: 13px; color: #999; margin-left: 10px">
        源码:
        <a href="https://github.com/xiangyuecn/Recorder/blob/master/assets/zdemo.index.realtime_voice.js" target="_blank">
          /assets/zdemo.index.realtime_voice.js
        </a>
      </span>
        </div>
        <div style="border-bottom: 1px solid #ddd; padding: 5px">
            <div>
                ws(s)：<input v-model="wsApi" style="width: 260px; padding: 5px 3px; border: 1px solid #ddd" />
            </div>
            <div style="font-size: 13px; color: #999">
                需要先在电脑上运行Recorder仓库
                <a href="https://github.com/xiangyuecn/Recorder/tree/master/assets/node-localServer" target="_blank">
                    /assets/node-localServer
                </a>
                内的nodejs服务器端脚本，然后填写你电脑局域网ip即可测试（用127.0.0.1时可用ws），支持ws、wss测试WebSocket地址
            </div>
            <div>
                <span>我的标识</span>
                <input v-model="wsID1" style="width: 50px; padding: 5px 3px; border: 1px solid #ddd; vertical-align: middle" />
                <button @click="wsConnClick" style="vertical-align: middle">连接服务器</button>
                <button @click="wsDisconnClick" style="vertical-align: middle">断开</button>
            </div>
        </div>
        <div style="border-bottom: 1px solid #ddd; padding: 5px">
            <div style="font-size: 13px; color: #999">服务器将pcm片段实时发送给客户端，模拟播放语音流</div>
            <label style="font-size: 13px">
                <input type="checkbox" v-model="readAudio" /> 读audio-16k.wav
            </label>
            <button @click="wsAudioStartClick" style="vertical-align: middle">播放语音流</button>
            <button @click="wsAudioStopClick" style="vertical-align: middle">结束</button>
            <div>{{ audioFrameMsg }}</div>
        </div>
        <div style="padding: 5px">
            <div style="font-size: 13px; color: #999">
                语音通话聊天对讲，请在上面进行录音操作，音频数据会实时传送给对方播放（实时pcm）
            </div>
            <span>对方标识</span>
            <input v-model="wsID2" style="width: 50px; padding: 5px 3px; border: 1px solid #ddd; vertical-align: middle" />
            <button @click="wsOpenVoiceClick" style="vertical-align: middle">开始通话</button>
            <button @click="wsCloseVoiceClick" style="vertical-align: middle">结束</button>
            <div>{{ voiceReceiveMsg }}</div>
            <div>{{ voiceSendMsg }}</div>
        </div>
    </div>
</template>

<script>
import Recorder from 'recorder-core'; // 引入Recorder库

export default {
  data() {
    return {
      wsApi: '',
      wsID1: '',
      wsID2: '',
      readAudio: false,
      audioFrameMsg: '',
      voiceReceiveMsg: '',
      voiceSendMsg: '',
      socket: null,
      socketIsOpen: false,
      recorder: null, // 录音器实例
      ws_voiceSendUserCount: 0,
      ws_voiceSendOKCount: 0,
      ws_voiceSendErrCount: 0,
      ws_voiceSendSize: 0,
      ws_voiceSendDur: 0,
      ws_voiceReceiveCount: 0,
      ws_voiceReceiveSize: 0,
      ws_voiceReceiveDur: 0,
    };
  },
  methods: {
    // 连接WebSocket
    wsConnClick() {
      if (!this.wsApi || !/^wss?:\/\/.+/i.test(this.wsApi)) {
        this.log('请配置正确的ws地址', 1);
        return;
      }
      if (!this.wsID1) {
        this.log('请填写我的标识', 1);
        return;
      }
      this.log(`正在连接${this.wsApi}...`);
      this.socket = new WebSocket(this.wsApi);
      this.socket.binaryType = 'arraybuffer';
      this.socket.onopen = this.onWsOpen;
      this.socket.onmessage = this.onWsMessage;
      this.socket.onclose = this.onWsClose;
      this.socket.onerror = this.onWsError;
    },
    // WebSocket打开
    onWsOpen() {
      this.socketIsOpen = true;
      this.log('ws已连接', 2);
      this.wsSendMessage('setMeta', { uid: this.wsID1 });
    },
    // WebSocket消息
    onWsMessage(event) {
      if (typeof event.data === 'string') {
        this.log(`收到消息: ${event.data}`);
      } else {
        this.log('收到二进制消息');
      }
    },
    // WebSocket关闭
    onWsClose() {
      this.socketIsOpen = false;
      this.log('ws已断开');
    },
    // WebSocket错误
    onWsError(error) {
      this.socketIsOpen = false;
      this.log(`ws发生错误: ${error.message}`, 1);
    },
    // 断开WebSocket
    wsDisconnClick() {
      if (this.socket) {
        this.socket.close();
      }
    },
    // 发送消息
    wsSendMessage(type, data) {
      if (!this.socketIsOpen) {
        this.log('ws连接未打开', 1);
        return;
      }
      const message = JSON.stringify({ type, data });
      this.socket.send(message);
    },
    // 开始播放语音流
    wsAudioStartClick() {
      if (!this.socketIsOpen) {
        this.log('ws未连接', 1);
        return;
      }
      this.wsSendMessage('audioStart', { readAudio: this.readAudio });
    },
    // 结束播放语音流
    wsAudioStopClick() {
      if (!this.socketIsOpen) {
        this.log('ws未连接', 1);
        return;
      }
      this.wsSendMessage('audioStop', {});
    },
    // 开始语音通话
    wsOpenVoiceClick() {
      if (!this.socketIsOpen) {
        this.log('ws未连接', 1);
        return;
      }

      const err = this.readWsID2();
      if (err) {
        this.log(err, 1);
        return;
      }

      this.log('我方已开始语音发送，请在上面进行录音操作', 2);

      // 调用录音授权请求
      this.recreq((err) => {
        if (!err) {
          recstart();
        }
      });

      let lastIdx = 1e9;
      let chunk = null;

      // 实时处理录音数据
      window.RtVoiceProcess = (buffers, powerLevel, duration, sampleRate, newBufferIdx) => {
        if (lastIdx > newBufferIdx) {
          chunk = null; // 重新录音，重置环境
        }
        lastIdx = newBufferIdx;

        // 采样率转换，处理PCM数据
        chunk = Recorder.SampleData(buffers, sampleRate, 16000, chunk);
        const pcm = chunk.data;

        // 将PCM数据转为二进制格式
        const bytes = new Uint8Array(pcm.buffer);

        // 发送PCM数据
        this.ws__sendMessage(
          'sendTo',
          {
            toMetaKey: 'uid',
            toMetaValue: this.wsID2, // 接收方信息
            sendType: 'custom_voiceFrame',
            sendData: {
              fromMetaKey: 'uid',
              fromMetaValue: this.wsID1, // 发送方信息
              sampleRate: 16000, // 采样率
            },
          },
          bytes,
          null,
          (data) => {
            this.ws_voiceSendUserCount = data.count;
            if (data.count) {
              this.ws_voiceSendOKCount++;
            } else {
              this.ws_voiceSendErrCount++;
            }
            this.ws_voiceSendSize += pcm.byteLength;
            this.ws_voiceSendDur = Math.round(
              (this.ws_voiceSendSize / 2 / 16000) * 1000,
            );
            this.ws_voiceSendDurTxt = this.formatTime(this.ws_voiceSendDur);
            this.setData();
          },
        );
      };

      this.resetWsSendVoice();
    },
    // 重置语音发送状态
    resetWsSendVoice() {
      this.ws_voiceSendUserCount = 0;
      this.ws_voiceSendOKCount = 0;
      this.ws_voiceSendErrCount = 0;
      this.ws_voiceSendSize = 0;
      this.ws_voiceSendDur = 0;
      this.ws_voiceSendDurTxt = '00:00';
      this.setData();
    },
    // 结束语音发送
    wsCloseVoiceClick() {
      window.RtVoiceProcess = null;
      this.log('我方已结束语音发送');
    },
    // 读取对方标识
    readWsID2() {
      if (!this.wsID2) {
        return '对方标识不能为空';
      }
      return null;
    },
    // WebSocket发送消息
    ws__sendMessage(type, meta, bytes, callback, onResponse) {
      this.socket.send(JSON.stringify({ type, meta, bytes }));
      setTimeout(() => {
        onResponse({ count: 1 });
      }, 1000);
    },
    // 录音授权请求函数
    recreq(callback) {
      // 假设这是一个简单的录音授权请求
      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({ audio: true })
          .then((stream) => {
            this.recorder = Recorder(stream); // 使用录音库初始化
            callback(null);
          })
          .catch((err) => {
            callback(err);
          });
      } else {
        callback(new Error('录音功能不支持'));
      }
    },
    log(message, type = 0) {
      console.log(message); // 日志打印，实际可以做更多处理
    },
    formatTime(duration) {
      const minutes = Math.floor(duration / 60000);
      const seconds = Math.floor((duration % 60000) / 1000);
      return `${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
    },
    setData() {
      this.$forceUpdate();
    },
  },
};
</script>
